﻿using System;
using System.Linq;
using System.Text.RegularExpressions;

namespace Dora.Tools.Utility
{
    public class IdChecker
    {
        //中国居民身份证校验码算法
        //    步骤如下：
        //将身份证号码前面的17位数分别乘以不同的系数。从第一位到第十七位的系数分别为：7－9－10－5－8－4－2－1－6－3－7－9－10－5－8－4－2。
        //将这17位数字和系数相乘的结果相加。
        //用加出来和除以11，取余数。
        //余数只可能有0－1－2－3－4－5－6－7－8－9－10这11个数字。其分别对应的最后一位身份证的号码为1－0－X－9－8－7－6－5－4－3－2。
        //通过上面计算得知如果余数是3，第18位的校验码就是9。如果余数是2那么对应的校验码就是X，X实际是罗马数字10。
        //例如：某男性的身份证号码为【53010219200508011x】， 我们看看这个身份证是不是合法的身份证。首先我们得出前17位的乘积和【(5*7)+(3*9)+(0*10)+(1*5)+(0*8)+(2*4)+(1*2)+(9*1)+(2*6)+(0*3)+(0*7)+(5*9)+(0*10)+(8*5)+(0*8)+(1*4)+(1*2)】是189，然后用189除以11得出的结果是189/11=17----2，也就是说其余数是2。最后通过对应规则就可以知道余数2对应的检验码是X。所以，可以判定这是一个正确的身份证号码。
        
        /**
         * 18位二代身份证号码的正则表达式
         */
        public static readonly string RegexIdNo18 = "^"
                                                    + "\\d{6}" // 6位地区码
                                                    + "(18|19|([23]\\d))\\d{2}" // 年YYYY
                                                    + "((0[1-9])|(10|11|12))" // 月MM
                                                    + "(([0-2][1-9])|10|20|30|31)" // 日DD
                                                    + "\\d{3}" // 3位顺序码
                                                    + "[0-9Xx]" // 校验码
                                                    + "$";

        /**
         * 15位一代身份证号码的正则表达式
         */
        public static readonly string RegexIdNo15 = "^"
                                                    + "\\d{6}" // 6位地区码
                                                    + "\\d{2}" // 年YYYY
                                                    + "((0[1-9])|(10|11|12))" // 月MM
                                                    + "(([0-2][1-9])|10|20|30|31)" // 日DD
                                                    + "\\d{3}" // 3位顺序码
                                                    + "$";

        // 加权因子
        private static readonly int[] W = {7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2};


        /// <summary>
        ///     不确定长度的身份证校验方法，
        ///     内部增加了异常捕获
        /// </summary>
        /// <param name="idNumber"></param>
        /// <returns>成功返回空字符串，失败返回异常信息</returns>
        public static string ChekcId(string idNumber)
        {
            if (string.IsNullOrWhiteSpace(idNumber)) return "身份证号码不能为空";
            idNumber = idNumber.Trim();
            var isSuccess = false;
            try
            {
                switch (idNumber.Length)
                {
                    case 15:
                        isSuccess = CheckIdNo15(idNumber);
                        break;
                    case 18:
                        isSuccess = CheckIdNo(idNumber);
                        break;
                    default:
                        return "错误的身份证号码长度";
                }
            }
            catch (Exception e)
            {
                //Console.WriteLine(e.Message);
            }

            return isSuccess ? "" : "错误的身份证号码";
        }

        /// <summary>
        ///     确定的18位身份证号码检验
        /// </summary>
        /// <param name="idNo18"></param>
        /// <returns></returns>
        public static bool CheckIdNo(string idNo18)
        {
            if (idNo18 == null) throw new ArgumentNullException(nameof(idNo18));
            // 校验身份证号码的长度
            if (string.IsNullOrWhiteSpace(idNo18) || idNo18.Trim().Length != 18) throw new Exception("错误的身份证号码长度");
            // 匹配身份证号码的正则表达式
            if (!RegexMatch(idNo18, RegexIdNo18)) throw new Exception("不合法的18位身份证号码");
            // 校验身份证号码的验证码
            return ValidateCheckNumber(idNo18);
        }

        /// <summary>
        ///     确定的15位身份证号码检验
        /// </summary>
        /// <param name="idNo15"></param>
        /// <returns></returns>
        public static bool CheckIdNo15(string idNo15)
        {
            if (idNo15 == null) throw new ArgumentNullException(nameof(idNo15));
            // 校验身份证号码的长度
            if (string.IsNullOrWhiteSpace(idNo15) || idNo15.Trim().Length != 15) throw new Exception("错误的身份证号码长度");
            // 匹配身份证号码的正则表达式
            if (!RegexMatch(idNo15, RegexIdNo15)) throw new Exception("不合法的15位身份证号码");

            return true;

            // 校验身份证号码的验证码
        }

        /// <summary>
        ///     15位一代身份证号码升级18位二代身份证号码
        ///     为15位的一代身份证号码增加年份的前2位和最后1位校验码
        /// </summary>
        /// <param name="idNo15"></param>
        /// <returns>返回18位身份证号，需要增加异常捕获，可能的异常信息为检验结果</returns>
        public static string UpdateIdNo15To18(string idNo15)
        {
            if (!CheckIdNo15(idNo15)) return "";
            // 得到本体码，因一代身份证皆为19XX年生人，年份中增加19，组成4位
            var masterNumber = idNo15.Substring(0, 6) + "19" + idNo15.Substring(6);
            // 计算校验码
            var checkNumber = ComputeCheckNumber(masterNumber);
            // 返回本体码+校验码=完整的身份证号码
            return masterNumber + checkNumber;
        }

        /**
         * 匹配正则表达式
         * 
         * @param inputString 字符串
         * @param regex 正则表达式
         * @return true - 校验通过<br>
         *         false - 校验不通过
         */
        private static bool RegexMatch(string inputString, string regex)
        {
            var re = new Regex(regex);
            return re.IsMatch(inputString);
        }


        /// <summary>
        ///     校验码校验 适用于18位的二代身份证号码
        /// </summary>
        /// <param name="idNo18"></param>
        /// <returns>
        ///     true - 校验通过
        ///     false - 校验不通过
        /// </returns>
        private static bool ValidateCheckNumber(string idNo18)
        {
            if (idNo18 == null) throw new ArgumentNullException(nameof(idNo18));
            // 加权因子
            var idNoArray = idNo18.ToCharArray();
            var sum = W.Select((t, i) => int.Parse(idNoArray[i].ToString()) * t).Sum();
            // 校验位是X，则表示10
            if (idNoArray[17].ToString().ToLower() == "x")
                sum += 10;
            else
                sum += int.Parse(idNoArray[17].ToString());

            // 如果除11模1，则校验通过
            return sum % 11 == 1;
        }


        /**
         * 计算校验码
         * <p>
         * 适用于18位的二代身份证号码
         * </p>
         * 
         * @param masterNumber 本体码
         * @return 校验码
         */
        private static string ComputeCheckNumber(string masterNumber)
        {
            var masterNumberArray = masterNumber.ToCharArray();
            var sum = W.Select((t, i) => int.Parse(masterNumberArray[i].ToString()) * t).Sum();
            // 根据同余定理得到的校验码数组
            string[] checkNumberArray = {"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"};
            // 得到校验码
            var checkNumber = checkNumberArray[sum % 11];
            // 返回校验码
            return checkNumber;
        }
    }
}